/* * SoapUI, Copyright (C) 2004-2016 SmartBear Software * * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent * versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * * http://ec.europa.eu/idabc/eupl * * Unless required by applicable law or agreed to in writing, software distributed under the Licence is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the Licence for the specific language governing permissions and limitations * under the Licence. */ package com.eviware.soapui.support.components; import com.eviware.soapui.SoapUI; import com.eviware.soapui.support.PropertyChangeNotifier; import com.eviware.soapui.support.StringUtils; import com.eviware.soapui.support.UISupport; import com.eviware.soapui.support.swing.JTableFactory; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.PropertyUtils; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.DefaultCellEditor; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JPasswordField; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.TransferHandler; import javax.swing.UIDefaults; import javax.swing.UIManager; import javax.swing.border.TitledBorder; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableModel; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; /** * Table for displaying property name/value pairs * * @author Ole.Matzura */ @SuppressWarnings("serial") public class JPropertiesTable<T> extends JPanel { public final static Object[] BOOLEAN_OPTIONS = new Object[]{Boolean.TRUE, Boolean.FALSE}; private PropertiesTableModel<T> tableModel; private JTable table; private TitledBorder titledBorder; private String title; public JPropertiesTable(String title) { this(title, null); } public JPropertiesTable(String title, T propertyObject) { super(new BorderLayout()); this.title = title; setBackground(Color.WHITE); tableModel = new PropertiesTableModel<T>(propertyObject); table = new PTable(tableModel); table.setBackground(Color.WHITE); table.getColumnModel().getColumn(0).setHeaderValue("Property"); table.getColumnModel().getColumn(1).setHeaderValue("Value"); table.getColumnModel().getColumn(0).setCellRenderer(new PropertiesTableCellRenderer()); table.getColumnModel().getColumn(1).setCellRenderer(new PropertiesTableCellRenderer()); add(new JScrollPane(table), BorderLayout.CENTER); titledBorder = BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(), title); /* * Java 7 issue * * old: titledBorder.setTitleFont( titledBorder.getTitleFont().deriveFont( * Font.PLAIN, 11 ) ); titledBorder.getTitleFont() return null in Java 7 */ Font defaultUIFont = getUIDefaultFont(); if (defaultUIFont != null) { titledBorder.setTitleFont(getUIDefaultFont().deriveFont(Font.PLAIN, 11)); } if (title != null) { setBorder(titledBorder); } table.setBackground(Color.WHITE); setPreferredSize(table.getPreferredSize()); } private Font getUIDefaultFont() { UIDefaults uidefs = UIManager.getLookAndFeelDefaults(); for (Object key : uidefs.keySet()) { if (uidefs.get(key) instanceof Font) { return uidefs.getFont(key); } } return null; } public void setTitle(String title) { this.title = title; titledBorder.setTitle(title); setBorder(titledBorder); repaint(); } public String getTitle() { return title; } @Override public void removeNotify() { getTableModel().release(); super.removeNotify(); } @Override public void addNotify() { getTableModel().attach(); super.addNotify(); } public void setPropertyObject(T propertyObject) { if (table.isEditing()) { table.getCellEditor().stopCellEditing(); } tableModel.setPropertyObject(propertyObject); } public PropertiesTableModel<?> getTableModel() { return tableModel; } public PropertyDescriptor addProperty(String caption, String name) { return addProperty(caption, name, false); } public PropertyDescriptor addProperty(String caption, String name, boolean editable) { return addProperty(caption, name, editable, null); } public PropertyDescriptor addProperty(String caption, String name, boolean editable, PropertyFormatter formatter) { return tableModel.addProperty(caption, name, editable, formatter); } public static final class PropertiesTableModel<T> extends AbstractTableModel implements PropertyChangeListener { private List<PropertyDescriptor> properties = new ArrayList<PropertyDescriptor>(); private T propertyObject; private boolean attached; public PropertiesTableModel(T propertyObject) { this.propertyObject = propertyObject; } public void attach() { if (!attached && propertyObject instanceof PropertyChangeNotifier) { ((PropertyChangeNotifier) propertyObject).addPropertyChangeListener(this); attached = true; } } public void setPropertyObject(T propertyObject) { release(); this.propertyObject = propertyObject; attach(); fireTableDataChanged(); } public PropertyDescriptor addProperty(String caption, String name, boolean editable, PropertyFormatter formatter) { PropertyDescriptor propertyDescriptor = new PropertyDescriptor(caption, name, editable, formatter); properties.add(propertyDescriptor); return propertyDescriptor; } public PropertyDescriptor addProperty(String caption, String name, Object[] options) { PropertyDescriptor propertyDescriptor = new PropertyDescriptor(caption, name, options); properties.add(propertyDescriptor); return propertyDescriptor; } public int getRowCount() { return properties.size(); } public int getColumnCount() { return 2; } public boolean isCellEditable(int rowIndex, int columnIndex) { if (columnIndex == 0 || propertyObject == null) { return false; } return properties.get(rowIndex).isEditable() && PropertyUtils.isWriteable(propertyObject, properties.get(rowIndex).getName()); } public void setValueAt(Object aValue, int rowIndex, int columnIndex) { try { if (propertyObject != null && columnIndex == 1 && properties.get(rowIndex).isEditable()) { BeanUtils.setProperty(propertyObject, properties.get(rowIndex).getName(), aValue); } } catch (IllegalAccessException e) { SoapUI.logError(e); } catch (InvocationTargetException e) { SoapUI.logError(e); } } public Object getValueAt(int rowIndex, int columnIndex) { if (propertyObject == null) { return null; } try { PropertyDescriptor propertyDescriptor = properties.get(rowIndex); switch (columnIndex) { case 0: return propertyDescriptor.getCaption(); case 1: { Object value = PropertyUtils.getSimpleProperty(propertyObject, propertyDescriptor.getName()); return propertyDescriptor.getFormatter().format(propertyDescriptor.getName(), value); } } } catch (IllegalAccessException e) { SoapUI.logError(e); } catch (InvocationTargetException e) { SoapUI.logError(e); } catch (NoSuchMethodException e) { SoapUI.logError(e); } return null; } public PropertyDescriptor getPropertyDescriptorAt(int row) { return properties.get(row); } public void propertyChange(PropertyChangeEvent evt) { fireTableDataChanged(); } public void release() { if (propertyObject instanceof PropertyChangeNotifier && attached) { ((PropertyChangeNotifier) propertyObject).removePropertyChangeListener(this); attached = false; } } public PropertyDescriptor addPropertyShadow(String caption, String name, boolean editable) { PropertyDescriptor propertyDescriptor = new PropertyDescriptor(caption, name, editable); properties.add(propertyDescriptor); return propertyDescriptor; } } public static class PropertyDescriptor { private final String caption; private final String name; private boolean editable; private PropertyFormatter formatter; private Object[] options; private DefaultCellEditor cellEditor; private String description; public PropertyDescriptor(String caption, String name, boolean editable, PropertyFormatter formatter) { this.caption = caption; this.name = name; this.editable = editable; this.formatter = formatter; JTextField textField = new JTextField(); textField.setBorder(BorderFactory.createEmptyBorder()); cellEditor = new DefaultCellEditor(textField); } public PropertyDescriptor(String caption, String name, Object[] options) { this.caption = caption; this.name = name; this.options = options; editable = true; JComboBox comboBox = new JComboBox(options); if (options.length > 0 && options[0] == null) { comboBox.setEditable(true); comboBox.removeItemAt(0); } comboBox.setBorder(null); cellEditor = new DefaultCellEditor(comboBox); } /** * For password field in table. * * @param caption * @param name * @param editable * @author robert nemet */ public PropertyDescriptor(String caption, String name, boolean editable) { this.caption = caption; this.name = name; this.editable = editable; JPasswordField textField = new JPasswordField(); textField.setBorder(BorderFactory.createEmptyBorder()); cellEditor = new DefaultCellEditor(textField); } public void setFormatter(PropertyFormatter formatter) { this.formatter = formatter; } public PropertyFormatter getFormatter() { return formatter == null ? DefaultFormatter.getInstance() : formatter; } public String getCaption() { return caption; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public boolean isEditable() { return editable; } public Object[] getOptions() { return options; } public boolean hasOptions() { return options != null; } public String getName() { return name; } public TableCellEditor getCellEditor() { return cellEditor; } } private static class PropertiesTableCellRenderer extends DefaultTableCellRenderer { public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component component; DefaultCellEditor cellEditor = (DefaultCellEditor) table.getCellEditor(row, column); if (cellEditor.getComponent() instanceof JPasswordField && value instanceof String) { if (value != null && ((String) value).length() > 0) { component = super.getTableCellRendererComponent(table, "**************", isSelected, hasFocus, row, column); } else { component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); } } else { component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); } if (component instanceof JComponent) { PropertyDescriptor descriptor = ((PropertiesTableModel<?>) table.getModel()) .getPropertyDescriptorAt(row); if (StringUtils.hasContent(descriptor.getDescription())) { ((JComponent) component).setToolTipText(descriptor.getDescription()); } // do not set tooltip as value for password field, it has no sense. else if (value != null && StringUtils.hasContent(value.toString()) && !(cellEditor.getComponent() instanceof JPasswordField)) { ((JComponent) component).setToolTipText(value.toString()); } else { ((JComponent) component).setToolTipText(null); } } return component; } } /** * Formatter used for displaying property values * * @author Ole.Matzura */ public interface PropertyFormatter { public Object format(String propertyName, Object value); } private static class DefaultFormatter implements PropertyFormatter { private static PropertyFormatter instance; public static PropertyFormatter getInstance() { if (instance == null) { instance = new DefaultFormatter(); } return instance; } public Object format(String propertyName, Object value) { return value; } } public PropertyDescriptor addProperty(String caption, String name, Object[] options) { return tableModel.addProperty(caption, name, options); } private class PTable extends JTable { public PTable(TableModel tableModel) { super(tableModel); getActionMap().put(TransferHandler.getCopyAction().getValue(Action.NAME), new AbstractAction() { public void actionPerformed(ActionEvent e) { int row = getSelectedRow(); if (row == -1) { return; } StringSelection selection = new StringSelection(getValueAt(row, 1).toString()); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, selection); } }); putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); if (UISupport.isMac()) { setShowGrid(false); setIntercellSpacing(new Dimension(0, 0)); } } @Override public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { Component defaultRenderer = super.prepareRenderer(renderer, row, column); if (UISupport.isMac()) { JTableFactory.applyStripesToRenderer(row, defaultRenderer); } return defaultRenderer; } @Override public boolean getShowVerticalLines() { return !UISupport.isMac(); } public TableCellEditor getCellEditor(int row, int column) { if (column == 0) { return super.getCellEditor(row, column); } else { return tableModel.getPropertyDescriptorAt(row).getCellEditor(); } } } /** * Value in this field will not be showen. It will be masked... * * @param caption * @param name * @param editable * @return * @author robert nemet */ public PropertyDescriptor addPropertyShadow(String caption, String name, boolean editable) { return tableModel.addPropertyShadow(caption, name, editable); } }